@parall/sdk 1.56.1 → 1.57.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attachment-client.d.ts +2 -2
- package/dist/attachment-client.d.ts.map +1 -1
- package/dist/attachment-client.js +2 -2
- package/dist/browser-use.d.ts +288 -0
- package/dist/browser-use.d.ts.map +1 -0
- package/dist/browser-use.js +6 -0
- package/dist/client.d.ts +14 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +35 -29
- package/dist/constants.d.ts +12 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +22 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/llm-provider-client.d.ts +38 -0
- package/dist/llm-provider-client.d.ts.map +1 -0
- package/dist/llm-provider-client.js +57 -0
- package/dist/llm-provider-types.d.ts +97 -0
- package/dist/llm-provider-types.d.ts.map +1 -0
- package/dist/llm-provider-types.js +6 -0
- package/dist/types.d.ts +56 -70
- package/dist/types.d.ts.map +1 -1
- package/dist/wiki-changeset.d.ts +4 -0
- package/dist/wiki-changeset.d.ts.map +1 -0
- package/dist/wiki-changeset.js +11 -0
- package/dist/wiki-upload.d.ts +43 -0
- package/dist/wiki-upload.d.ts.map +1 -0
- package/dist/wiki-upload.js +87 -0
- package/package.json +1 -1
- package/src/attachment-client.ts +2 -2
- package/src/browser-use.ts +217 -0
- package/src/client.ts +60 -30
- package/src/constants.ts +22 -0
- package/src/index.ts +8 -0
- package/src/llm-provider-client.ts +96 -0
- package/src/llm-provider-types.ts +104 -0
- package/src/types.ts +83 -101
- package/src/wiki-changeset.ts +13 -0
- package/src/wiki-upload.ts +142 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Org-configured model providers (BYOK) and the model rows bound to them.
|
|
3
|
+
*
|
|
4
|
+
* Design: docs/engineering-design/org-llm-provider-byok-design.md
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Inbound protocol a provider can serve. */
|
|
8
|
+
export type LLMProviderEndpoint = 'chat_completions' | 'messages' | 'responses';
|
|
9
|
+
|
|
10
|
+
export type LLMProviderStatus = 'active' | 'disabled';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A provider as the server returns it. The credential is never echoed back —
|
|
14
|
+
* `api_key_set` is the only thing a client learns about it.
|
|
15
|
+
*/
|
|
16
|
+
export interface LLMProvider {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
base_url: string;
|
|
20
|
+
api_key_set: boolean;
|
|
21
|
+
endpoints: LLMProviderEndpoint[];
|
|
22
|
+
status: LLMProviderStatus;
|
|
23
|
+
is_platform: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Runtime types whose protocol this provider can actually serve. Anything
|
|
26
|
+
* absent here silently uses the platform provider — surface this, or an
|
|
27
|
+
* operator will assume their credential covers every agent.
|
|
28
|
+
*/
|
|
29
|
+
runtimes: string[];
|
|
30
|
+
/** Most recent fallback-worthy failure, if any. */
|
|
31
|
+
last_error_at?: string | null;
|
|
32
|
+
last_error_status?: number | null;
|
|
33
|
+
created_at: string;
|
|
34
|
+
updated_at: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CreateLLMProviderRequest {
|
|
38
|
+
name: string;
|
|
39
|
+
base_url: string;
|
|
40
|
+
api_key: string;
|
|
41
|
+
endpoints: LLMProviderEndpoint[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Omitting `api_key` leaves the stored credential untouched. */
|
|
45
|
+
export interface UpdateLLMProviderRequest {
|
|
46
|
+
name?: string;
|
|
47
|
+
base_url?: string;
|
|
48
|
+
api_key?: string;
|
|
49
|
+
endpoints?: LLMProviderEndpoint[];
|
|
50
|
+
status?: LLMProviderStatus;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* One org's row for a model. Overriding a platform model and adding a new one
|
|
55
|
+
* are the same shape: `overrides_platform` is derived by the server from
|
|
56
|
+
* whether the platform catalog knows this id, never stored.
|
|
57
|
+
*/
|
|
58
|
+
export interface OrgLLMModel {
|
|
59
|
+
id: string;
|
|
60
|
+
model_id: string;
|
|
61
|
+
provider_id: string;
|
|
62
|
+
upstream_model_id: string;
|
|
63
|
+
display_name?: string | null;
|
|
64
|
+
context_window?: number | null;
|
|
65
|
+
max_tokens?: number | null;
|
|
66
|
+
overrides_platform: boolean;
|
|
67
|
+
created_at: string;
|
|
68
|
+
updated_at: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CreateOrgLLMModelRequest {
|
|
72
|
+
model_id: string;
|
|
73
|
+
provider_id: string;
|
|
74
|
+
/** The name to send to this provider, which differs per provider. */
|
|
75
|
+
upstream_model_id: string;
|
|
76
|
+
/** Required when `model_id` is not in the platform catalog. */
|
|
77
|
+
display_name?: string;
|
|
78
|
+
context_window?: number;
|
|
79
|
+
max_tokens?: number;
|
|
80
|
+
effort_levels?: string[];
|
|
81
|
+
input_modalities?: string[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Everything about a model row except its identity. `model_id` is what agents
|
|
86
|
+
* pin to, so changing it is a delete-and-recreate the operator performs
|
|
87
|
+
* deliberately; the server rejects it here.
|
|
88
|
+
*/
|
|
89
|
+
export interface UpdateOrgLLMModelRequest {
|
|
90
|
+
provider_id?: string;
|
|
91
|
+
upstream_model_id?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Metadata fields accept `null` to clear the org's value and go back to
|
|
94
|
+
* inheriting the platform entry's. Omitting a field leaves it as it is —
|
|
95
|
+
* the two are different requests, so do not send `undefined` meaning
|
|
96
|
+
* "clear". A model the platform does not have cannot clear these: it has
|
|
97
|
+
* nothing to inherit from, and the server rejects it.
|
|
98
|
+
*/
|
|
99
|
+
display_name?: string | null;
|
|
100
|
+
context_window?: number | null;
|
|
101
|
+
max_tokens?: number | null;
|
|
102
|
+
effort_levels?: string[] | null;
|
|
103
|
+
input_modalities?: string[] | null;
|
|
104
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -481,6 +481,25 @@ export interface Organization {
|
|
|
481
481
|
onboarding_agent_id: string | null;
|
|
482
482
|
/** Org-level IANA timezone (e.g. "America/New_York"). Defaults to "UTC". */
|
|
483
483
|
timezone: string;
|
|
484
|
+
/**
|
|
485
|
+
* Whether this org may configure its own LLM providers (BYOK). Absent means
|
|
486
|
+
* it inherits the platform default, which is off.
|
|
487
|
+
*
|
|
488
|
+
* An entitlement, not a feature flag: only a platform admin changes it, and
|
|
489
|
+
* it is expected to become plan-driven. Turning it off also stops existing
|
|
490
|
+
* provider configuration from taking effect.
|
|
491
|
+
*/
|
|
492
|
+
llm_byok_enabled?: boolean | null;
|
|
493
|
+
/**
|
|
494
|
+
* When an org's own model provider fails at run time, retry once on the
|
|
495
|
+
* platform provider — billed to this org's credit balance. Defaults to off,
|
|
496
|
+
* so "no fallback" is the default state and fallback is opted into.
|
|
497
|
+
* Does NOT gate the capability fallback: a provider that cannot serve a
|
|
498
|
+
* runtime's protocol always defers to the platform regardless.
|
|
499
|
+
* Current servers populate this field; it remains optional for source
|
|
500
|
+
* compatibility with cached and rolling-upgrade organization payloads.
|
|
501
|
+
*/
|
|
502
|
+
llm_byok_fallback_enabled?: boolean;
|
|
484
503
|
created_at: string;
|
|
485
504
|
/** Per-member flag: true when the user has dismissed the onboarding popup for this org. */
|
|
486
505
|
onboarding_dismissed: boolean;
|
|
@@ -710,11 +729,35 @@ export interface CreateInvitationRequest {
|
|
|
710
729
|
role?: OrgMemberRole;
|
|
711
730
|
}
|
|
712
731
|
|
|
713
|
-
// Response of
|
|
714
|
-
// returned exactly once at mint time and can never
|
|
732
|
+
// Response of API key create/regenerate (agent keys and personal keys alike).
|
|
733
|
+
// The plaintext `api_key` is returned exactly once at mint time and can never
|
|
734
|
+
// be fetched again. Personal keys additionally echo the org they are bound to.
|
|
715
735
|
export interface ApiKey {
|
|
716
736
|
id: string;
|
|
717
737
|
api_key: string;
|
|
738
|
+
org_id?: string;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// A personal API key row as listed by GET /users/me/api-keys. Personal keys
|
|
742
|
+
// are org-scoped ("org_full"): the user's permissions inside org_id only.
|
|
743
|
+
// Managed Clip keys have their own surface; the key hash never leaves the
|
|
744
|
+
// server.
|
|
745
|
+
export interface PersonalApiKey {
|
|
746
|
+
id: string;
|
|
747
|
+
user_id: string;
|
|
748
|
+
name?: string;
|
|
749
|
+
org_id: string;
|
|
750
|
+
last_used_at?: string;
|
|
751
|
+
expires_at?: string;
|
|
752
|
+
created_at: string;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
export interface CreatePersonalApiKeyRequest {
|
|
756
|
+
name?: string;
|
|
757
|
+
/** The org this key is bound to; the caller must be a member. */
|
|
758
|
+
org_id: string;
|
|
759
|
+
/** RFC3339; must be in the future. Omit for a non-expiring key. */
|
|
760
|
+
expires_at?: string;
|
|
718
761
|
}
|
|
719
762
|
|
|
720
763
|
export interface CreateAgentResponse {
|
|
@@ -1057,10 +1100,8 @@ export interface MessageDispatchSource {
|
|
|
1057
1100
|
source_type: string;
|
|
1058
1101
|
source_id: string;
|
|
1059
1102
|
/**
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
1062
|
-
* transport; a value older than the current New Session barrier is
|
|
1063
|
-
* rejected (STALE_GENERATION).
|
|
1103
|
+
* @deprecated Ignored by current servers. Retained so clients compiled
|
|
1104
|
+
* against older SDKs can upgrade without source changes.
|
|
1064
1105
|
*/
|
|
1065
1106
|
generation?: number;
|
|
1066
1107
|
}
|
|
@@ -1797,8 +1838,8 @@ export interface UpdateTaskRequest {
|
|
|
1797
1838
|
* Lane-bound form (v1 consumers): dispatch_lane + dispatch_event_id bind
|
|
1798
1839
|
* the update to the claimed typed lane (incumbency-checked).
|
|
1799
1840
|
* By-id form (parel v2 converged consumers — no lane exists):
|
|
1800
|
-
* dispatch_event_id
|
|
1801
|
-
*
|
|
1841
|
+
* dispatch_event_id alone binds the update to the WorkItem; the server
|
|
1842
|
+
* admits it on the binding's transport and deletion state alone.
|
|
1802
1843
|
* In both forms dispatch_effect_key additionally commits the update as
|
|
1803
1844
|
* the dispatch's idempotent Effect and resolves the WorkItem in the same
|
|
1804
1845
|
* transaction. Lane-bound: exactly "task_update:<dispatch_event_id>" —
|
|
@@ -1811,6 +1852,10 @@ export interface UpdateTaskRequest {
|
|
|
1811
1852
|
dispatch_lane?: string;
|
|
1812
1853
|
dispatch_event_id?: string;
|
|
1813
1854
|
dispatch_effect_key?: string;
|
|
1855
|
+
/**
|
|
1856
|
+
* @deprecated Ignored by current servers. Retained so clients compiled
|
|
1857
|
+
* against older SDKs can upgrade without source changes.
|
|
1858
|
+
*/
|
|
1814
1859
|
dispatch_generation?: number;
|
|
1815
1860
|
}
|
|
1816
1861
|
|
|
@@ -2391,6 +2436,10 @@ export interface WikiChangeset {
|
|
|
2391
2436
|
title: string;
|
|
2392
2437
|
message: string | null;
|
|
2393
2438
|
status: WikiChangesetStatus;
|
|
2439
|
+
/** Present on current servers; optional keeps older mocked/client data source-compatible. */
|
|
2440
|
+
head_branch?: string | null;
|
|
2441
|
+
/** Present on current servers; optional keeps older mocked/client data source-compatible. */
|
|
2442
|
+
base_commit?: string | null;
|
|
2394
2443
|
merge_commit: string | null;
|
|
2395
2444
|
file_changes: WikiFileChange[];
|
|
2396
2445
|
changed_paths: string[];
|
|
@@ -2658,6 +2707,8 @@ export interface CreateWikiChangesetRequest {
|
|
|
2658
2707
|
title: string;
|
|
2659
2708
|
message?: string;
|
|
2660
2709
|
file_changes: WikiFileChangeInput[];
|
|
2710
|
+
/** Omit for the server default (`proposed`); pass `draft` for staged uploads. */
|
|
2711
|
+
status?: 'draft' | 'proposed';
|
|
2661
2712
|
source_chat_id?: string;
|
|
2662
2713
|
source_message_id?: string;
|
|
2663
2714
|
source_run_id?: string;
|
|
@@ -2667,6 +2718,8 @@ export interface UpdateWikiChangesetRequest {
|
|
|
2667
2718
|
title?: string;
|
|
2668
2719
|
message?: string;
|
|
2669
2720
|
file_changes?: WikiFileChangeInput[];
|
|
2721
|
+
/** Author-settable transitions currently include `proposed` and `closed`. */
|
|
2722
|
+
status?: 'proposed' | 'closed';
|
|
2670
2723
|
/**
|
|
2671
2724
|
* When true, file_changes define the changeset's FULL content: the server
|
|
2672
2725
|
* resets the feature branch to the current default-branch HEAD before
|
|
@@ -2832,17 +2885,6 @@ export interface WikiBlob {
|
|
|
2832
2885
|
signed_url_expires_at?: string;
|
|
2833
2886
|
}
|
|
2834
2887
|
|
|
2835
|
-
export type WikiStoredAs = 'git_blob' | 'lfs_pointer';
|
|
2836
|
-
|
|
2837
|
-
/** Response from POST /uploads or POST /changesets/{csId}/files. */
|
|
2838
|
-
export interface WikiFileUploadResponse {
|
|
2839
|
-
path: string;
|
|
2840
|
-
size: number;
|
|
2841
|
-
stored_as: WikiStoredAs;
|
|
2842
|
-
commit_sha: string;
|
|
2843
|
-
content_sha: string;
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
2888
|
/** Response from POST /files/preview-url — short-lived signed URL for
|
|
2847
2889
|
* browser `<img>`/`<video>`/`<iframe>` src. TTL is 5 minutes by default. */
|
|
2848
2890
|
export interface WikiFilePreviewUrlResponse {
|
|
@@ -3365,6 +3407,7 @@ export interface ChannelConversation {
|
|
|
3365
3407
|
agent_id: string;
|
|
3366
3408
|
agent_session_id?: string | null;
|
|
3367
3409
|
external_conversation_id: string;
|
|
3410
|
+
external_conversation_name?: string;
|
|
3368
3411
|
external_thread_id: string;
|
|
3369
3412
|
conversation_type: string;
|
|
3370
3413
|
external_user_id: string;
|
|
@@ -4161,6 +4204,8 @@ export interface ResolvedRef {
|
|
|
4161
4204
|
type: string;
|
|
4162
4205
|
exists: boolean;
|
|
4163
4206
|
restricted?: boolean;
|
|
4207
|
+
/** The target could not be resolved because a dependency failed temporarily. */
|
|
4208
|
+
unavailable?: boolean;
|
|
4164
4209
|
|
|
4165
4210
|
// User fields
|
|
4166
4211
|
display_name?: string;
|
|
@@ -5496,88 +5541,25 @@ export interface BrowserAliasExecutionReceipt {
|
|
|
5496
5541
|
updated_at: string;
|
|
5497
5542
|
}
|
|
5498
5543
|
|
|
5499
|
-
export type
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
export interface BrowserUseTarget {
|
|
5519
|
-
tab_id?: string;
|
|
5520
|
-
browser_generation?: number;
|
|
5521
|
-
document_generation?: number;
|
|
5522
|
-
snapshot_id?: string;
|
|
5523
|
-
ref?: string;
|
|
5524
|
-
}
|
|
5525
|
-
|
|
5526
|
-
export interface BrowserUseOperationRequest {
|
|
5527
|
-
contract_version: 'browser-use-operation-v1';
|
|
5528
|
-
request_id: string;
|
|
5529
|
-
profile_id: string;
|
|
5530
|
-
action: BrowserUseAction;
|
|
5531
|
-
target?: BrowserUseTarget;
|
|
5532
|
-
params: Record<string, unknown>;
|
|
5533
|
-
/** Canonical RFC3339 UTC instant, no more than two minutes in the future. */
|
|
5534
|
-
deadline: string;
|
|
5535
|
-
}
|
|
5536
|
-
|
|
5537
|
-
export type BrowserUseOperationPhase =
|
|
5538
|
-
| 'accepted'
|
|
5539
|
-
| 'started'
|
|
5540
|
-
| 'succeeded'
|
|
5541
|
-
| 'failed'
|
|
5542
|
-
| 'outcome_unknown';
|
|
5543
|
-
|
|
5544
|
-
export interface BrowserUseOperationError {
|
|
5545
|
-
code: string;
|
|
5546
|
-
message?: string;
|
|
5547
|
-
hint?: string;
|
|
5548
|
-
}
|
|
5549
|
-
|
|
5550
|
-
/** Durable Browser Use proof. `data` is encrypted/transient: it is present
|
|
5551
|
-
* only while result_available is true and disappears after result_expires_at;
|
|
5552
|
-
* the terminal receipt and result_digest remain durable. */
|
|
5553
|
-
export interface BrowserUseOperationReceipt {
|
|
5554
|
-
contract_version: 'browser-use-operation-v1';
|
|
5555
|
-
operation_id: string;
|
|
5556
|
-
request_id: string;
|
|
5557
|
-
profile_id: string;
|
|
5558
|
-
profile_version: number;
|
|
5559
|
-
profile_config_revision: number;
|
|
5560
|
-
action: BrowserUseAction;
|
|
5561
|
-
required_grant: 'browser.read' | 'browser.interact' | 'browser.advanced';
|
|
5562
|
-
phase: BrowserUseOperationPhase;
|
|
5563
|
-
dispatched?: boolean;
|
|
5564
|
-
receipt_id?: string;
|
|
5565
|
-
receipt_digest?: string;
|
|
5566
|
-
occurred_at?: string;
|
|
5567
|
-
tab_id?: string;
|
|
5568
|
-
browser_generation?: number;
|
|
5569
|
-
duration_ms?: number;
|
|
5570
|
-
result_digest?: string;
|
|
5571
|
-
result_available: boolean;
|
|
5572
|
-
result_expires_at?: string;
|
|
5573
|
-
data?: unknown;
|
|
5574
|
-
error?: BrowserUseOperationError;
|
|
5575
|
-
deadline: string;
|
|
5576
|
-
accepted_at: string;
|
|
5577
|
-
started_at?: string;
|
|
5578
|
-
terminal_at?: string;
|
|
5579
|
-
updated_at: string;
|
|
5580
|
-
}
|
|
5544
|
+
export type {
|
|
5545
|
+
BrowserUseAction,
|
|
5546
|
+
BrowserUseActionRequest,
|
|
5547
|
+
BrowserUseArticle,
|
|
5548
|
+
BrowserUseGrant,
|
|
5549
|
+
BrowserUseOperationError,
|
|
5550
|
+
BrowserUseOperationPhase,
|
|
5551
|
+
BrowserUseOperationReceipt,
|
|
5552
|
+
BrowserUseOperationReceiptBase,
|
|
5553
|
+
BrowserUseOperationReceiptFor,
|
|
5554
|
+
BrowserUseOperationRequest,
|
|
5555
|
+
BrowserUseRefTarget,
|
|
5556
|
+
BrowserUseSuccessDataMap,
|
|
5557
|
+
BrowserUseTabState,
|
|
5558
|
+
BrowserUseTabSummary,
|
|
5559
|
+
BrowserUseTabTarget,
|
|
5560
|
+
BrowserUseTarget,
|
|
5561
|
+
BrowserUseViewport,
|
|
5562
|
+
} from './browser-use.js';
|
|
5581
5563
|
|
|
5582
5564
|
/**
|
|
5583
5565
|
* Sanitized per-profile egress proxy status (hosted Cloud Profiles) — the GET
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WikiChangeset } from './types.js';
|
|
2
|
+
|
|
3
|
+
/** Normalize fields that older wiki-service responses may omit. */
|
|
4
|
+
export function normalizeWikiChangeset(changeset: WikiChangeset): WikiChangeset {
|
|
5
|
+
return {
|
|
6
|
+
...changeset,
|
|
7
|
+
head_branch: changeset.head_branch ?? null,
|
|
8
|
+
base_commit: changeset.base_commit ?? null,
|
|
9
|
+
merge_commit: changeset.merge_commit ?? null,
|
|
10
|
+
changed_paths: changeset.changed_paths ?? [],
|
|
11
|
+
file_changes: changeset.file_changes ?? [],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
interface WikiFileUploadParamsBase {
|
|
2
|
+
path: string;
|
|
3
|
+
file: Blob;
|
|
4
|
+
message?: string;
|
|
5
|
+
/** Stable per-file retry key; replays return the original successful path. */
|
|
6
|
+
uploadId?: string;
|
|
7
|
+
/** Preserve the legacy 409 by default, or atomically choose `name (N).ext`. */
|
|
8
|
+
conflict?: 'error' | 'rename';
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
onProgress?: (uploadedBytes: number, totalBytes: number) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type WikiStoredAs = 'git_blob' | 'lfs_pointer';
|
|
14
|
+
|
|
15
|
+
/** Response from POST /uploads or POST /changesets/{csId}/files. */
|
|
16
|
+
export interface WikiFileUploadResponse {
|
|
17
|
+
path: string;
|
|
18
|
+
size: number;
|
|
19
|
+
stored_as: WikiStoredAs;
|
|
20
|
+
commit_sha: string;
|
|
21
|
+
content_sha: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parameters for the maintain-only direct upload endpoint. */
|
|
25
|
+
export type WikiFileUploadParams = WikiFileUploadParamsBase;
|
|
26
|
+
|
|
27
|
+
/** Parameters for a changeset-scoped multipart upload. */
|
|
28
|
+
export interface WikiChangesetFileUploadParams extends WikiFileUploadParamsBase {
|
|
29
|
+
/** `auto` accepts reviewable text as a Git blob on the changeset branch. */
|
|
30
|
+
contentRoute?: 'binary' | 'auto';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createWikiUploadFormData(
|
|
34
|
+
params: WikiFileUploadParams | WikiChangesetFileUploadParams,
|
|
35
|
+
): FormData {
|
|
36
|
+
const form = new FormData();
|
|
37
|
+
form.append('path', params.path);
|
|
38
|
+
form.append('file', params.file);
|
|
39
|
+
if (params.message) form.append('message', params.message);
|
|
40
|
+
if (params.uploadId) form.append('upload_id', params.uploadId);
|
|
41
|
+
if (params.conflict) form.append('conflict', params.conflict);
|
|
42
|
+
if ('contentRoute' in params && params.contentRoute) {
|
|
43
|
+
form.append('content_route', params.contentRoute);
|
|
44
|
+
}
|
|
45
|
+
return form;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface MultipartTransportOptions {
|
|
49
|
+
method: string;
|
|
50
|
+
url: string;
|
|
51
|
+
headers: Record<string, string>;
|
|
52
|
+
body: FormData;
|
|
53
|
+
timeoutMs: number;
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
onProgress?: (uploadedBytes: number, totalBytes: number) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Send one multipart request. XHR is used only when a browser caller asks
|
|
59
|
+
* for upload progress; fetch remains the transport everywhere else. Auth
|
|
60
|
+
* refresh and API error decoding stay in ParallClient above this boundary. */
|
|
61
|
+
export function sendMultipartRequest(options: MultipartTransportOptions): Promise<Response> {
|
|
62
|
+
if (options.onProgress && typeof XMLHttpRequest !== 'undefined') {
|
|
63
|
+
return multipartXHR(options, options.onProgress);
|
|
64
|
+
}
|
|
65
|
+
const timeoutSignal = AbortSignal.timeout(options.timeoutMs);
|
|
66
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal;
|
|
67
|
+
return fetch(options.url, {
|
|
68
|
+
method: options.method,
|
|
69
|
+
headers: options.headers,
|
|
70
|
+
body: options.body,
|
|
71
|
+
signal,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function multipartXHR(
|
|
76
|
+
options: MultipartTransportOptions,
|
|
77
|
+
onProgress: (uploadedBytes: number, totalBytes: number) => void,
|
|
78
|
+
): Promise<Response> {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const xhr = new XMLHttpRequest();
|
|
81
|
+
let settled = false;
|
|
82
|
+
|
|
83
|
+
const finish = (fn: () => void) => {
|
|
84
|
+
if (settled) return;
|
|
85
|
+
settled = true;
|
|
86
|
+
options.signal?.removeEventListener('abort', abortFromSignal);
|
|
87
|
+
fn();
|
|
88
|
+
};
|
|
89
|
+
const abortFromSignal = () => xhr.abort();
|
|
90
|
+
|
|
91
|
+
xhr.open(options.method, options.url, true);
|
|
92
|
+
xhr.timeout = options.timeoutMs;
|
|
93
|
+
for (const [name, value] of Object.entries(options.headers)) {
|
|
94
|
+
xhr.setRequestHeader(name, value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
xhr.upload.onprogress = (event) => {
|
|
98
|
+
try {
|
|
99
|
+
onProgress(event.loaded, event.lengthComputable ? event.total : 0);
|
|
100
|
+
} catch {
|
|
101
|
+
// Observer failures must not cancel an otherwise healthy upload.
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
xhr.onload = () =>
|
|
105
|
+
finish(() => {
|
|
106
|
+
const responseHeaders = new Headers();
|
|
107
|
+
for (const line of xhr
|
|
108
|
+
.getAllResponseHeaders()
|
|
109
|
+
.trim()
|
|
110
|
+
.split(/[\r\n]+/)) {
|
|
111
|
+
if (!line) continue;
|
|
112
|
+
const separator = line.indexOf(':');
|
|
113
|
+
if (separator > 0) {
|
|
114
|
+
responseHeaders.append(
|
|
115
|
+
line.slice(0, separator).trim(),
|
|
116
|
+
line.slice(separator + 1).trim(),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const responseBody =
|
|
121
|
+
xhr.status === 204 || xhr.responseText === '' ? null : xhr.responseText;
|
|
122
|
+
resolve(
|
|
123
|
+
new Response(responseBody, {
|
|
124
|
+
status: xhr.status,
|
|
125
|
+
statusText: xhr.statusText,
|
|
126
|
+
headers: responseHeaders,
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
xhr.onerror = () => finish(() => reject(new TypeError('Network request failed')));
|
|
131
|
+
xhr.ontimeout = () =>
|
|
132
|
+
finish(() => reject(new DOMException('Request timed out', 'TimeoutError')));
|
|
133
|
+
xhr.onabort = () => finish(() => reject(new DOMException('Request aborted', 'AbortError')));
|
|
134
|
+
|
|
135
|
+
if (options.signal?.aborted) {
|
|
136
|
+
finish(() => reject(new DOMException('Request aborted', 'AbortError')));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
options.signal?.addEventListener('abort', abortFromSignal, { once: true });
|
|
140
|
+
xhr.send(options.body);
|
|
141
|
+
});
|
|
142
|
+
}
|