Skip to content

feat(functions): add Functions service for invoking coded functions [PLT-106037] - #614

Open
amrit-agarwal-1 wants to merge 5 commits into
mainfrom
feat/sdk-plt-106037
Open

feat(functions): add Functions service for invoking coded functions [PLT-106037]#614
amrit-agarwal-1 wants to merge 5 commits into
mainfrom
feat/sdk-plt-106037

Conversation

@amrit-agarwal-1

@amrit-agarwal-1 amrit-agarwal-1 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Method Added

Layer Method Signature
Service functions.getAll() getAll<T extends FunctionGetAllOptions>(options?: T): Promise<T extends HasPaginationOptions<T> ? PaginatedResponse<FunctionGetResponse> : NonPaginatedResponse<FunctionGetResponse>>
Service functions.invoke() invoke<TInput extends object, TOutput>(name: string, input?: TInput, options?: FunctionInvokeOptions): Promise<TOutput>
Bound fn.invoke() invoke<TInput extends object, TOutput>(input?: TInput): Promise<TOutput>

Endpoint Called

Method HTTP Endpoint OAuth Scope
getAll() GET orchestrator_/odata/HttpTriggers OR.Default
invoke() GET orchestrator_/odata/HttpTriggers (name lookup) OR.Default
invoke() GET orchestrator_/odata/Folders({id}) (folder key resolution) OR.Folders or OR.Folders.Read
invoke() POST/GET orchestrator_/t/{folderKey}/{processSlug}/{slug} (HTTP trigger) OR.Default
  • New modular service @uipath/uipath-typescript/functions. Internal class FunctionService, public alias Functions. Extends FolderScopedService.
  • A coded function is packaged as a process and exposed through an HTTP (API) trigger; this service masks that so pro devs work with functions directly rather than composing Processes + Triggers.
  • Folder context is accepted as folderId, folderKey, or folderPath (or resolved from the SDK's init-time folder context), via the shared resolveFolderHeaders utility.
  • getAll() supports OData pagination ($top, $skip, $count) and filtering; a function maps 1:1 to an HTTP trigger, so listing is done at the trigger level.
  • invoke() is composite (see flow below). It calls the HTTP trigger directly — not Processes.start() — so HTTP-mode function context is preserved. Invocation is synchronous (the platform long-polls the job and returns the output as the HTTP response body). Functions declared with Get receive input as query parameters; all other verbs receive it as the JSON body.

Example Usage

import { UiPath } from '@uipath/uipath-typescript/core';
import { Functions } from '@uipath/uipath-typescript/functions';

const sdk = new UiPath(config);
await sdk.initialize();
const functions = new Functions(sdk);

// Discover functions in a folder
const all = await functions.getAll({ folderId: <folderId> });

// Invoke by name
const result = await functions.invoke('hello', { name: 'Alice' }, { folderId: <folderId> });

// Typed input/output, folder by path
interface HelloInput { name: string }
interface HelloOutput { message: string }
const typed = await functions.invoke<HelloInput, HelloOutput>(
  'hello',
  { name: 'Alice' },
  { folderPath: 'Shared/Finance' }
);
console.log(typed.message);

// Bound invoke on a discovered function
const hello = all.items.find(f => f.name === 'hello');
if (hello) {
  const out = await hello.invoke({ name: 'Alice' });
}

API Response vs SDK Response

getAll — Transform pipeline

pascalToCamelCaseKeys(row) → manual reshape to RawFunctionGetResponse (flattens nested release) → createFunctionWithMethods() (attaches bound invoke)

Field mapping (getAll, raw HttpTriggers row → SDK)

API Response (PascalCase) SDK Response (camelCase) Change Reason
OrganizationUnitId folderId Case + Rename Standard folder rename
OrganizationUnitFullyQualifiedName folderName Case + Rename Standard folder rename
ReleaseKey processKey Case + Rename Release = process in SDK terms
Release.Name processName Reshape Flatten nested release
Release.Slug processSlug Reshape Flatten nested release
Id, Name, Slug, Method, Description, Enabled, InputArguments, EntryPointPath id, name, slug, method, description, enabled, inputArguments, entryPointPath Case camelCase conversion
JobPriority, RunAsCaller, CallingMode, MachineRobots, … (~25 job-runner fields) Dropped Platform plumbing, not function DX

invoke — Composition flow

invoke(name, input?, {folderId|folderKey|folderPath}?)
  1. GET  /odata/HttpTriggers  ($filter=Name eq '<name>', folder-scoped)  -> slug, method, processSlug
  2. resolve folder key:
       options.folderKey  ->  use it
       else SDK-init folder context (no explicit folder)  ->  use it
       else GET /odata/Folders({folderId})  ->  Key   (cached)
  3. POST /t/{folderKey}/{processSlug}/{slug}   (body = input ?? {})
       (Get-method functions -> GET with input as query params)
     -> HTTP response body = function output (typed as TOutput)

Internal types (not exported)

Type Purpose
RawFunctionTrigger Raw /odata/HttpTriggers row (camelCased) before reshape
RawFolderResponse Minimal /odata/Folders({id}) shape — folder Key only

Sample SDK Response

getAll()
{
  "items": [
    {
      "id": "e758581f-2f78-4d86-a8e9-f4bc3aad52ec",
      "name": "hello",
      "slug": "hello",
      "method": "Post",
      "description": "Returns a greeting message.",
      "enabled": true,
      "inputArguments": "{\"name\":\"World\"}",
      "entryPointPath": "content/functions/hello.ts",
      "processKey": "d1519612-2961-488e-af7a-7379cc1c3544",
      "processName": "my-functions",
      "processSlug": "my-functions",
      "folderId": 2843389,
      "folderName": "Shared/Solution 121 new"
    }
  ],
  "totalCount": 1
}
invoke('hello', { name: 'Alice' }, { folderId })
{ "message": "Hello, Alice!" }

🤖 Auto-generated using onboarding skills

@amrit-agarwal-1
amrit-agarwal-1 requested a review from a team July 20, 2026 10:08
@amrit-agarwal-1 amrit-agarwal-1 changed the title feat(functions): add Functions service for invoking coded functions [PLT-106037] [Draft] feat(functions): add Functions service for invoking coded functions [PLT-106037] Jul 20, 2026
@amrit-agarwal-1 amrit-agarwal-1 changed the title [Draft] feat(functions): add Functions service for invoking coded functions [PLT-106037] feat(functions): add Functions service for invoking coded functions [PLT-106037] Jul 28, 2026
amrit-agarwal-1 and others added 4 commits July 28, 2026 18:11
…PLT-106037]

Adds a modular `@uipath/uipath-typescript/functions` service that discovers
and invokes JS/Python coded functions, masking the underlying process + HTTP
trigger plumbing.

- getAll(options?): folder-scoped, paginated listing over /odata/HttpTriggers
- invoke(name, input?, options?): resolves the function by name, resolves the
  folder key, and calls the function's HTTP trigger; returns typed output
- bound invoke() on each getAll item

Folder context accepted as folderId / folderKey / folderPath (or SDK-init
folder context). Verified end-to-end against a live tenant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on [PLT-106037]

- invoke() now follows the gateway's 303 status long-poll chain for functions
  exceeding the ~25s response window: explicit redirect handling, fresh auth
  token per poll leg, bounded by a new maxWaitSeconds option (default 300)
  with a clear timeout error. Browsers fall back to the engine's automatic
  redirect handling (manual redirects are not readable there).
- New jobKey option on invoke: sent as X-UIPATH-JobKey so the run is recorded
  with the parent job's key (parentJobKey) and inherits its context and
  licensing transaction. Applied to the invocation leg only.
- ApiClient: RequestSpec.redirect passthrough and a raw response type so the
  invoke leg can observe the 303 chain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…okup errors [PLT-106037]

Coded functions over HTTP are request/response only, so the long-running
handling is removed: no platform branch, no manual 303 loop, no job
polling. invoke() issues one request and returns the output, letting the
engine follow any redirect. Drops the maxWaitSeconds option.

Also:
- filter on processName / processSlug now maps to the Release navigation
  path, so functions can be narrowed to one package server-side
- a name lookup miss lists the folder's function names, since the usual
  cause is passing a package name
- ApiClient no longer sends Content-Type on GET/HEAD, which Orchestrator
  rejects for GET function invocations

Co-Authored-By: Claude <noreply@anthropic.com>
Drops machinery left behind by the abandoned 303 long-poll invoke approach and
tightens the surface the PR adds, so the diff reflects only what the feature needs.

Shared core (reverts unused additions to the HTTP layer):
- RESPONSE_TYPES.RAW, the 'raw' member of ResponseType, and the RAW passthrough
  branch in ApiClient — nothing ever set responseType 'raw'. The platform
  long-polls the job server-side and returns the output as the response body, so
  no caller drives a redirect chain.
- RequestSpec.redirect and its `redirect: options.redirect` fetch passthrough —
  no call site ever set it.

Functions service:
- release.id in RawFunctionTrigger: declared but never read, contradicting the
  type's own "only the fields consumed" contract.
- fn.method.toUpperCase() at the invoke call site: BaseService.request() already
  uppercases the verb.
- Private helpers took `options?` and defended with `options ?? {}` / `options?.x`
  while their only call site always passes a defined object; narrowed to a
  required FolderScopedOptions.
- The "and N more" suffix on the name-lookup error could only ever render
  "and 1 more" ($top caps the response one row past the limit), so it now says
  "and more".
- resourceType label 'Function.getByName' named a method the service does not
  expose; now 'Functions.invoke', matching the convention used elsewhere.
- The subpath barrel exported the class under both Functions and FunctionService;
  only the public alias is exported now, matching Notifications and Governance.
  The unit test imports the class from the implementation file instead.

Docs:
- Every FunctionServiceModel @example called methods on `fns`, a variable no
  example declares (the Usage block declares `functions`). Examples now use
  `functions` throughout, with the getAll result renamed to `deployed` where it
  would otherwise shadow it.
- The module @example used a literal folderId; now the <folderId> placeholder.

Tests:
- Deleted functions.browser.test.ts. Its only distinguishing assertion was
  `expect.not.objectContaining({ redirect: 'manual' })` against the now-removed
  field, and the service has no platform branch, so the rest duplicated
  functions.test.ts.
- Removed the unused ERROR_FUNCTION_NOT_FOUND test constant.

Also drops an unrelated "AI App Builders" mkdocs nav section that leaked into
this branch; it points at pages that live on the docs/ai-app-builders branch and
would have published five dead nav entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://UiPath.github.io/uipath-typescript/pr-preview/pr-614/

Built to branch gh-pages at 2026-07-28 21:18 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

* `processSlug`, or `processKey` to narrow the result to a single package.
*
* @param options - Query options including folder scoping (`folderId` / `folderKey` / `folderPath`), filtering, and pagination options
* @returns Promise resolving to either an array of functions {@link NonPaginatedResponse}<{@link FunctionGetResponse}> or a {@link PaginatedResponse}<{@link FunctionGetResponse}> when pagination options are used.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned {@link} tag — this line renders as stray text (or a dangling link) in TypeDoc output. It appears to be a copy-paste artifact; the @returns description on the line above already includes the two relevant links. Remove this line.

Suggested change
* @returns Promise resolving to either an array of functions {@link NonPaginatedResponse}<{@link FunctionGetResponse}> or a {@link PaginatedResponse}<{@link FunctionGetResponse}> when pagination options are used.

Comment thread tests/utils/mocks/functions.ts Outdated
Comment thread tests/utils/mocks/functions.ts Outdated
getAll: vi.fn(),
invoke: vi.fn(),
};
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convention requires afterEach(() => { vi.clearAllMocks(); }) even when mocks are recreated in beforeEach. The service test file includes this; the model test should too for consistency.

Suggested change
});
});
afterEach(() => {
vi.clearAllMocks();
});

Comment thread src/models/orchestrator/functions.constants.ts Outdated
@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review findings

Three minor convention violations flagged:

tests/utils/mocks/functions.ts line 10createMockRawFunctionTrigger uses Partial<any> / : any. Use Partial<Record<string, unknown>> / Record<string, unknown> instead (no-any rule).

tests/unit/models/orchestrator/functions.test.ts line 17 — Model test is missing afterEach(() => { vi.clearAllMocks(); }). The companion service test has it; convention requires it here too.

src/models/orchestrator/functions.constants.ts line 2 — JSDoc says "Field mapping for Function responses" but FunctionMap is never passed to transformData()toFunctionResponse does the reshape manually. The map is only consumed by transformOptions for OData filter/orderby rewriting. Updated comment proposed to prevent future confusion.

Everything else looks correct: @track decorators present on both public methods, JSDoc only on FunctionServiceModel (not duplicated on the class), barrel exports use export * from (not export type *), folder options destructured before PaginationHelpers.getAll, transform pipeline validated in both unit and integration tests, docs/oauth-scopes.md and docs/pagination.md updated, subpath export wired in package.json and rollup.config.js.

…06037]

- functions.test.ts called vi.unstubAllGlobals() in afterEach while stubbing no
  globals at all — the repo's only unpaired instance (jobs and
  conversational-agent each pair one unstub with 4-8 stubs).
- RawFunctionTrigger.folderName's "(null on list responses)" qualifier could not
  discriminate anything: the service only ever reads the HttpTriggers collection,
  so every response it sees is a list response.
- Dropped an orphaned {@link} line TypeDoc would render as stray text.
- createMockRawFunctionTrigger typed as Record<string, unknown> instead of any.
- Added the missing afterEach teardown to the model test.
- FunctionMap's comment claimed it mapped responses; it is only used by
  transformOptions for OData query rewriting.

Co-Authored-By: Claude <noreply@anthropic.com>
@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

✅ No issues found. Checked for bugs and CLAUDE.md compliance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant