Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d1c839093 | |||
| c5bb663bc7 | |||
| fc61bc42ad | |||
| 9b7488183d | |||
| 78f7c745a7 | |||
| c028dfb0ed | |||
| 214fd02b3b |
132
.github/workflows/watch-openapi.yml
vendored
Normal file
132
.github/workflows/watch-openapi.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
name: Watch Mistral OpenAPI Spec
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs daily at 06:00 UTC
|
||||
- cron: "0 6 * * *"
|
||||
workflow_dispatch: # manual trigger for testing
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check-spec:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Fetch and compare spec
|
||||
id: check
|
||||
run: |
|
||||
if ! curl --fail -sL https://raw.githubusercontent.com/mistralai/platform-docs-public/main/openapi.yaml -o new-spec.yaml; then
|
||||
echo "::error::Failed to download OpenAPI spec"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_HASH=$(sha256sum new-spec.yaml | cut -d' ' -f1)
|
||||
|
||||
if [ -f .openapi-hash ]; then
|
||||
OLD_HASH=$(cat .openapi-hash)
|
||||
else
|
||||
OLD_HASH="none"
|
||||
fi
|
||||
|
||||
echo "old=$OLD_HASH" >> "$GITHUB_OUTPUT"
|
||||
echo "new=$NEW_HASH" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$NEW_HASH" != "$OLD_HASH" ]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Diff against the in-tree baseline (.openapi-spec.yaml).
|
||||
# Maintainer commits a fresh baseline when addressing the issue.
|
||||
if [ -f .openapi-spec.yaml ]; then
|
||||
diff -u .openapi-spec.yaml new-spec.yaml > /tmp/spec-diff.txt || true
|
||||
else
|
||||
echo "No baseline .openapi-spec.yaml in repo - commit one to enable diffs." > /tmp/spec-diff.txt
|
||||
fi
|
||||
else
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Open or refresh tracking issue
|
||||
if: steps.check.outputs.changed == 'true'
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
OLD_HASH: ${{ steps.check.outputs.old }}
|
||||
NEW_HASH: ${{ steps.check.outputs.new }}
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
let diff = fs.readFileSync('/tmp/spec-diff.txt', 'utf8');
|
||||
|
||||
// Truncate if too long for issue body (GitHub issue body limit is 65536 chars)
|
||||
if (diff.length > 50000) {
|
||||
diff = diff.substring(0, 50000) + '\n\n... (truncated - view full spec for details)';
|
||||
}
|
||||
|
||||
const oldHash = process.env.OLD_HASH;
|
||||
const newHash = process.env.NEW_HASH;
|
||||
|
||||
const body =
|
||||
`The upstream OpenAPI spec has changed and the SDK is out of sync.\n\n` +
|
||||
`**Stored hash (.openapi-hash):** \`${oldHash}\`\n` +
|
||||
`**Current upstream hash:** \`${newHash}\`\n` +
|
||||
`_Last refreshed: ${new Date().toISOString()}_\n\n` +
|
||||
`[View upstream spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml)\n\n` +
|
||||
`### Diff vs in-tree baseline\n\`\`\`diff\n${diff}\n\`\`\`\n\n` +
|
||||
`### TODO\n` +
|
||||
`- [ ] Review spec changes\n` +
|
||||
`- [ ] Update SDK types if needed\n` +
|
||||
`- [ ] Run tests\n` +
|
||||
`- [ ] Bump \`.openapi-hash\` and refresh \`.openapi-spec.yaml\` in same commit\n` +
|
||||
`- [ ] Close this issue\n\n` +
|
||||
`<sub>This issue body is rewritten by the watcher each run, so the diff and hashes always reflect the latest upstream state.</sub>`;
|
||||
|
||||
const issues = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: 'openapi-update',
|
||||
state: 'open'
|
||||
});
|
||||
|
||||
if (issues.data.length === 0) {
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: 'Mistral OpenAPI spec has changed',
|
||||
body,
|
||||
labels: ['openapi-update']
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh the existing open issue so the diff/hashes never go stale.
|
||||
const issue = issues.data[0];
|
||||
|
||||
// Detect whether upstream moved *again* since the last refresh by
|
||||
// parsing the previous "Current upstream hash" out of the issue body.
|
||||
const marker = /\*\*Current upstream hash:\*\* `([a-f0-9]{64})`/;
|
||||
const prev = (issue.body || '').match(marker);
|
||||
const movedAgain = prev && prev[1] !== newHash;
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body
|
||||
});
|
||||
|
||||
if (movedAgain) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body:
|
||||
`Upstream spec moved again since this issue was last refreshed.\n\n` +
|
||||
`Previous upstream hash: \`${prev[1]}\`\n` +
|
||||
`New upstream hash: \`${newHash}\`\n\n` +
|
||||
`Issue body has been updated to the latest diff.`
|
||||
});
|
||||
}
|
||||
1
.openapi-hash
Normal file
1
.openapi-hash
Normal file
@@ -0,0 +1 @@
|
||||
ff8a7389b7a4e61145561361537aae37c49f7e2dcf7c4f79f41e80a30b484cc3
|
||||
19660
.openapi-spec.yaml
Normal file
19660
.openapi-spec.yaml
Normal file
File diff suppressed because it is too large
Load Diff
100
CHANGELOG.md
100
CHANGELOG.md
@@ -1,3 +1,103 @@
|
||||
## v1.4.0 — 2026-04-28
|
||||
|
||||
Spec/SDK alignment pass after upstream OpenAPI moved to v1.0.0 and
|
||||
Python SDK shipped v2.3.0..v2.4.3. RAG ingestion-pipeline beta surface
|
||||
(Python v2.4.3) intentionally deferred until the dust settles upstream.
|
||||
|
||||
### Added
|
||||
|
||||
- **`Client.GetWorkflowWorkerInfo`** — restores the
|
||||
`GET /v1/workflows/workers/whoami` endpoint that was removed in v1.3.0.
|
||||
The endpoint is still in the spec and is needed by callers running
|
||||
custom workers that connect their own scheduler.
|
||||
(`workflow.WorkerInfo` type.)
|
||||
- **Observability fields API** — three GETs missing since the
|
||||
observability surface was first added:
|
||||
- `Client.GetChatCompletionFields` (`/v1/observability/chat-completion-fields`)
|
||||
- `Client.GetChatCompletionFieldOptions` (`…/{field}/options?operator=…`)
|
||||
- `Client.GetChatCompletionFieldOptionsCounts` (`…/{field}/options-counts`)
|
||||
- new types: `observability.BaseFieldDefinition`, `FieldGroup`,
|
||||
`ChatCompletionFields`, `ChatCompletionFieldOptions`,
|
||||
`FieldOptionCountsRequest`, `FieldOptionCounts`, `FieldOptionCountItem`,
|
||||
plus `FieldType` and `FieldOperator` typed enums.
|
||||
- **Workflow payload encoding constants** — `workflow.EncodedPayloadOption`
|
||||
with `EncodedPayloadOffloaded`, `EncodedPayloadEncrypted`,
|
||||
`EncodedPayloadEncryptedPartial`. Wire-compatible refinement of the
|
||||
pre-existing `[]string` field on `NetworkEncodedInput`.
|
||||
(Mirrors Python SDK v2.4.0.)
|
||||
- **Workflow ↔ connector integration** (Python SDK v2.4.2):
|
||||
- `workflow.ConnectorSlot`, `ConnectorBindings`, `ConnectorExtensions`,
|
||||
`WorkflowExtensions` types.
|
||||
- `workflow.BuildConnectorExtensions(slots …)` helper that produces the
|
||||
nested map expected at `ExecutionRequest.Extensions["mistralai"]`.
|
||||
- `workflow.ConnectorAuthTaskState` + `ConnectorAuthStatus` constants
|
||||
for parsing payloads emitted by the `connector-auth` custom task event.
|
||||
- New `Extensions map[string]any` field on `workflow.ExecutionRequest`.
|
||||
- **HITL (human-in-the-loop) confirmation constants** — typed values
|
||||
alongside the pre-existing `conversation.ToolCallConfirmation` and
|
||||
`tool_confirmations` field:
|
||||
- `conversation.Confirmation` with `ConfirmationAllow` / `ConfirmationDeny`
|
||||
for the reply side.
|
||||
- `ConfirmationStatusPending` / `ConfirmationStatusAllowed` /
|
||||
`ConfirmationStatusDenied` for `FunctionCallEvent.ConfirmationStatus`
|
||||
and `FunctionCallEntry.ConfirmationStatus` (already present as
|
||||
untyped strings).
|
||||
|
||||
### Changed
|
||||
|
||||
- `workflow.NetworkEncodedInput.EncodingOptions` is now
|
||||
`[]EncodedPayloadOption` (string-typed alias). JSON wire format
|
||||
unchanged; existing call sites that passed `[]string{"offloaded"}`
|
||||
need to switch to `[]workflow.EncodedPayloadOption{workflow.EncodedPayloadOffloaded}`
|
||||
or the typed constants directly.
|
||||
- Tracking upstream Mistral OpenAPI spec **v1.0.0** (was v0.1.104).
|
||||
Only spec delta in this window was the removal of OCR confidence-score
|
||||
fields (`OCRPageObject.confidence_scores`,
|
||||
`OCRRequest.confidence_scores_granularity`,
|
||||
`OCRTableObject.word_confidence_scores`, plus the `OCRConfidenceScore`
|
||||
and `OCRPageConfidenceScores` schemas), none of which this SDK exposed.
|
||||
|
||||
### Fixed (CI)
|
||||
|
||||
- `watch-openapi.yml` no longer attempts to commit `.openapi-hash` /
|
||||
`.openapi-spec.yaml` to `main`. The push was being silently reverted
|
||||
by an upstream mirror, leaving the tracking issue stale across
|
||||
multiple upstream releases. The watcher now refreshes the open
|
||||
tracking issue's body on each run so the diff and hashes always
|
||||
reflect the current upstream state, and posts a comment when the
|
||||
spec moves again while the issue is still open.
|
||||
|
||||
## v1.3.0 — 2026-04-03
|
||||
|
||||
Upstream sync with Python SDK v2.3.0. Updates workflow registration model
|
||||
to reflect the managed deployment architecture and removes the deprecated
|
||||
workers endpoint.
|
||||
|
||||
### Added
|
||||
|
||||
- **`workflow.CodeDefinition`** — workflow interface metadata type with
|
||||
input/output schemas, signal/query/update handler definitions,
|
||||
determinism flag, and execution timeout.
|
||||
- **`workflow.SignalDefinition`**, **`QueryDefinition`**,
|
||||
**`UpdateDefinition`** — handler descriptor types.
|
||||
- **`Registration.Definition`** — code definition field on workflow
|
||||
registrations.
|
||||
- **`Registration.DeploymentID`** — replaces the worker/task-queue model
|
||||
with managed deployment references.
|
||||
- **`Registration.CompatibleWithChatAssistant`** — flag for chat assistant
|
||||
compatibility.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- **`Registration.TaskQueue`** — use `DeploymentID` instead. Will be
|
||||
removed in a future release.
|
||||
|
||||
### Removed (breaking)
|
||||
|
||||
- **`GetWorkflowWorkerInfo`** — the `/v1/workflows/workers/whoami` endpoint
|
||||
was removed upstream.
|
||||
- **`workflow.WorkerInfo`** — type no longer exists in the API.
|
||||
|
||||
## v1.2.1 — 2026-04-03
|
||||
|
||||
Move module path to `github.com/VikingOwl91/mistral-go-sdk` for public
|
||||
|
||||
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project
|
||||
|
||||
Idiomatic Go SDK for the Mistral AI API. Module path: `github.com/VikingOwl91/mistral-go-sdk`. Requires Go 1.26+. Zero external dependencies (stdlib only). Tracks the upstream [Mistral Python SDK](https://github.com/mistralai/client-python) as reference for API surface and type definitions.
|
||||
Idiomatic Go SDK for the Mistral AI API. Module path: `github.com/VikingOwl91/mistral-go-sdk`. Requires Go 1.26+. Zero external dependencies (stdlib only). Tracks the [official Mistral OpenAPI spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml) as primary reference for API surface and type definitions, with the [Mistral Python SDK](https://github.com/mistralai/client-python) as secondary reference for implementation patterns. A daily GitHub Action monitors the OpenAPI spec for changes.
|
||||
|
||||
## Repository layout
|
||||
|
||||
|
||||
25
README.md
25
README.md
@@ -11,7 +11,7 @@ The most complete Go client for the [Mistral AI API](https://docs.mistral.ai/).
|
||||
|
||||
**Zero dependencies.** The entire SDK — including tests — uses only the Go standard library. No `go.sum`, no transitive dependency tree to audit, no version conflicts, no supply chain risk.
|
||||
|
||||
**Full API coverage.** 166 methods across every Mistral endpoint — including Workflows, Connectors, Audio Speech/Voices, Conversations, Agents CRUD, Libraries, OCR, Observability, Fine-tuning, and Batch Jobs. No other Go SDK covers Workflows, Conversations, Connectors, or Observability.
|
||||
**Full API coverage.** 169 methods across every Mistral endpoint — including Workflows (with worker introspection and connector bindings), Connectors, Audio Speech/Voices, Conversations (including human-in-the-loop tool confirmations), Agents CRUD, Libraries, OCR, Observability (events, judges, datasets, campaigns, fields), Fine-tuning, and Batch Jobs. No other Go SDK covers Workflows, Conversations, Connectors, or Observability.
|
||||
|
||||
**Typed streaming.** A generic pull-based `Stream[T]` iterator — no channels, no goroutines, no leaks. Just `Next()` / `Current()` / `Err()` / `Close()`.
|
||||
|
||||
@@ -19,7 +19,7 @@ The most complete Go client for the [Mistral AI API](https://docs.mistral.ai/).
|
||||
|
||||
**Hand-written, not generated.** Idiomatic Go with sealed interfaces, discriminated unions, and functional options — not a Speakeasy/OpenAPI auto-gen dump with `any` everywhere.
|
||||
|
||||
**Test-driven.** 284 tests with race detection clean. Every endpoint tested against mock servers; integration tests against the real API.
|
||||
**Test-driven.** 297 tests with race detection clean. Every endpoint tested against mock servers; integration tests against the real API.
|
||||
|
||||
## Install
|
||||
|
||||
@@ -132,7 +132,7 @@ for stream.Next() {
|
||||
|
||||
## API Coverage
|
||||
|
||||
166 public methods on `Client`, grouped by domain:
|
||||
169 public methods on `Client`, grouped by domain:
|
||||
|
||||
| Domain | Methods |
|
||||
|--------|---------|
|
||||
@@ -156,6 +156,7 @@ for stream.Next() {
|
||||
| **Classification** | `Classify`, `ClassifyChat` |
|
||||
| **Observability (campaigns)** | `CreateCampaign`, `ListCampaigns`, `GetCampaign`, `DeleteCampaign`, `GetCampaignStatus`, `ListCampaignEvents` |
|
||||
| **Observability (events)** | `SearchChatCompletionEvents`, `SearchChatCompletionEventIDs`, `GetChatCompletionEvent`, `GetSimilarChatCompletionEvents`, `JudgeChatCompletionEvent` |
|
||||
| **Observability (fields)** | `GetChatCompletionFields`, `GetChatCompletionFieldOptions`, `GetChatCompletionFieldOptionsCounts` |
|
||||
| **Observability (judges)** | `CreateJudge`, `ListJudges`, `GetJudge`, `UpdateJudge`, `DeleteJudge`, `JudgeConversation` |
|
||||
| **Observability (datasets)** | `CreateDataset`, `ListDatasets`, `GetDataset`, `UpdateDataset`, `DeleteDataset`, `ExportDatasetToJSONL`, `ListDatasetRecords`, `CreateDatasetRecord`, `GetDatasetRecord`, `UpdateDatasetRecordPayload`, `UpdateDatasetRecordProperties`, `DeleteDatasetRecord`, `BulkDeleteDatasetRecords`, `JudgeDatasetRecord`, `ImportDatasetFromCampaign`, `ImportDatasetFromExplorer`, `ImportDatasetFromFile`, `ImportDatasetFromPlayground`, `ImportDatasetFromDataset`, `ListDatasetTasks`, `GetDatasetTask` |
|
||||
| **Workflows (CRUD)** | `ListWorkflows`, `GetWorkflow`, `UpdateWorkflow`, `ArchiveWorkflow`, `UnarchiveWorkflow`, `ExecuteWorkflow`, `ExecuteWorkflowAndWait` |
|
||||
@@ -236,14 +237,18 @@ if err != nil {
|
||||
|
||||
## Upstream Reference
|
||||
|
||||
This SDK tracks the [official Mistral Python SDK](https://github.com/mistralai/client-python)
|
||||
as its upstream reference for API surface and type definitions.
|
||||
This SDK tracks the [official Mistral OpenAPI spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml) as its primary reference for API surface and type definitions. A daily GitHub Action monitors the spec for changes and opens an issue when updates are detected.
|
||||
|
||||
| SDK Version | Upstream Python SDK |
|
||||
|-------------|---------------------|
|
||||
| v1.2.0 | v2.2.0 |
|
||||
| v1.1.0 | v2.1.3 |
|
||||
| v1.0.0 | v2.0.4 |
|
||||
The [Mistral Python SDK](https://github.com/mistralai/client-python) is used as a secondary reference for implementation patterns.
|
||||
|
||||
| SDK Version | Upstream Python SDK | Upstream OpenAPI |
|
||||
|-------------|---------------------|------------------|
|
||||
| v1.4.0 | v2.4.3 (excl. RAG ingestion-pipeline beta) | v1.0.0 |
|
||||
| v1.3.0 | v2.3.0 | v0.1.104 |
|
||||
| v1.2.1 | v2.2.0 | — |
|
||||
| v1.2.0 | v2.2.0 | — |
|
||||
| v1.1.0 | v2.1.3 | — |
|
||||
| v1.0.0 | v2.0.4 | — |
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -44,10 +44,30 @@ type CompletionArgs struct {
|
||||
ToolChoice *chat.ToolChoiceMode `json:"tool_choice,omitempty"`
|
||||
}
|
||||
|
||||
// Confirmation is a client decision on a pending tool call.
|
||||
type Confirmation string
|
||||
|
||||
const (
|
||||
ConfirmationAllow Confirmation = "allow"
|
||||
ConfirmationDeny Confirmation = "deny"
|
||||
)
|
||||
|
||||
// ConfirmationStatus values appear on FunctionCallEvent.ConfirmationStatus
|
||||
// and FunctionCallEntry.ConfirmationStatus, reporting where in the
|
||||
// human-in-the-loop flow a tool call currently sits.
|
||||
const (
|
||||
ConfirmationStatusPending = "pending"
|
||||
ConfirmationStatusAllowed = "allowed"
|
||||
ConfirmationStatusDenied = "denied"
|
||||
)
|
||||
|
||||
// ToolCallConfirmation confirms or denies a pending tool call.
|
||||
//
|
||||
// Send a slice of these on AppendRequest.ToolConfirmations after receiving
|
||||
// a function call event whose ConfirmationStatus is "pending".
|
||||
type ToolCallConfirmation struct {
|
||||
ToolCallID string `json:"tool_call_id"`
|
||||
Confirmation string `json:"confirmation"` // "allow" or "deny"
|
||||
Confirmation string `json:"confirmation"` // use ConfirmationAllow / ConfirmationDeny
|
||||
}
|
||||
|
||||
// Inputs represents conversation inputs (text string or entry array).
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// Version is the SDK version string.
|
||||
const Version = "1.2.1"
|
||||
const Version = "1.3.0"
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://api.mistral.ai"
|
||||
|
||||
84
observability/field.go
Normal file
84
observability/field.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package observability
|
||||
|
||||
// FieldType identifies the data type of a chat-completion-event field.
|
||||
type FieldType string
|
||||
|
||||
const (
|
||||
FieldTypeEnum FieldType = "ENUM"
|
||||
FieldTypeText FieldType = "TEXT"
|
||||
FieldTypeInt FieldType = "INT"
|
||||
FieldTypeFloat FieldType = "FLOAT"
|
||||
FieldTypeBool FieldType = "BOOL"
|
||||
FieldTypeTimestamp FieldType = "TIMESTAMP"
|
||||
FieldTypeArray FieldType = "ARRAY"
|
||||
)
|
||||
|
||||
// FieldOperator is a filter operator supported on observability fields.
|
||||
type FieldOperator string
|
||||
|
||||
const (
|
||||
FieldOperatorLT FieldOperator = "lt"
|
||||
FieldOperatorLTE FieldOperator = "lte"
|
||||
FieldOperatorGT FieldOperator = "gt"
|
||||
FieldOperatorGTE FieldOperator = "gte"
|
||||
FieldOperatorStartsWith FieldOperator = "startswith"
|
||||
FieldOperatorIStartsWith FieldOperator = "istartswith"
|
||||
FieldOperatorEndsWith FieldOperator = "endswith"
|
||||
FieldOperatorIEndsWith FieldOperator = "iendswith"
|
||||
FieldOperatorContains FieldOperator = "contains"
|
||||
FieldOperatorIContains FieldOperator = "icontains"
|
||||
FieldOperatorMatches FieldOperator = "matches"
|
||||
FieldOperatorNotContains FieldOperator = "notcontains"
|
||||
FieldOperatorINotContain FieldOperator = "inotcontains"
|
||||
FieldOperatorEq FieldOperator = "eq"
|
||||
FieldOperatorNeq FieldOperator = "neq"
|
||||
FieldOperatorIsNull FieldOperator = "isnull"
|
||||
FieldOperatorIncludes FieldOperator = "includes"
|
||||
FieldOperatorExcludes FieldOperator = "excludes"
|
||||
FieldOperatorLenEq FieldOperator = "len_eq"
|
||||
)
|
||||
|
||||
// BaseFieldDefinition describes a searchable chat-completion-event field.
|
||||
type BaseFieldDefinition struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Type FieldType `json:"type"`
|
||||
Group *string `json:"group,omitempty"`
|
||||
SupportedOperators []FieldOperator `json:"supported_operators"`
|
||||
}
|
||||
|
||||
// FieldGroup groups related field definitions for UI display.
|
||||
type FieldGroup struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
// ChatCompletionFields is the response of GET /v1/observability/chat-completion-fields.
|
||||
type ChatCompletionFields struct {
|
||||
FieldDefinitions []BaseFieldDefinition `json:"field_definitions"`
|
||||
FieldGroups []FieldGroup `json:"field_groups"`
|
||||
}
|
||||
|
||||
// ChatCompletionFieldOptions is the response of
|
||||
// GET /v1/observability/chat-completion-fields/{field_name}/options.
|
||||
//
|
||||
// Each option may be a string, bool, or null — preserved as raw any.
|
||||
type ChatCompletionFieldOptions struct {
|
||||
Options []any `json:"options"`
|
||||
}
|
||||
|
||||
// FieldOptionCountsRequest is the body of POST options-counts.
|
||||
type FieldOptionCountsRequest struct {
|
||||
FilterParams *FilterPayload `json:"filter_params,omitempty"`
|
||||
}
|
||||
|
||||
// FieldOptionCountItem pairs a field value with how many events have it.
|
||||
type FieldOptionCountItem struct {
|
||||
Value string `json:"value"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// FieldOptionCounts is the response of POST options-counts.
|
||||
type FieldOptionCounts struct {
|
||||
Counts []FieldOptionCountItem `json:"counts"`
|
||||
}
|
||||
46
observability_fields.go
Normal file
46
observability_fields.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/VikingOwl91/mistral-go-sdk/observability"
|
||||
)
|
||||
|
||||
// GetChatCompletionFields returns the searchable field definitions and groups
|
||||
// for chat-completion observability events.
|
||||
func (c *Client) GetChatCompletionFields(ctx context.Context) (*observability.ChatCompletionFields, error) {
|
||||
var resp observability.ChatCompletionFields
|
||||
if err := c.doJSON(ctx, "GET", "/v1/observability/chat-completion-fields", nil, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetChatCompletionFieldOptions returns the distinct values seen for the given
|
||||
// field, filtered by the requested operator.
|
||||
func (c *Client) GetChatCompletionFieldOptions(ctx context.Context, fieldName string, operator observability.FieldOperator) (*observability.ChatCompletionFieldOptions, error) {
|
||||
q := url.Values{}
|
||||
q.Set("operator", string(operator))
|
||||
path := fmt.Sprintf("/v1/observability/chat-completion-fields/%s/options?%s", url.PathEscape(fieldName), q.Encode())
|
||||
var resp observability.ChatCompletionFieldOptions
|
||||
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetChatCompletionFieldOptionsCounts returns per-value event counts for the
|
||||
// given field, optionally filtered by the supplied filter payload.
|
||||
func (c *Client) GetChatCompletionFieldOptionsCounts(ctx context.Context, fieldName string, req *observability.FieldOptionCountsRequest) (*observability.FieldOptionCounts, error) {
|
||||
if req == nil {
|
||||
req = &observability.FieldOptionCountsRequest{}
|
||||
}
|
||||
path := fmt.Sprintf("/v1/observability/chat-completion-fields/%s/options-counts", url.PathEscape(fieldName))
|
||||
var resp observability.FieldOptionCounts
|
||||
if err := c.doJSON(ctx, "POST", path, req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
100
observability_fields_test.go
Normal file
100
observability_fields_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/VikingOwl91/mistral-go-sdk/observability"
|
||||
)
|
||||
|
||||
func TestGetChatCompletionFields_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/v1/observability/chat-completion-fields" {
|
||||
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"field_definitions": []map[string]any{
|
||||
{
|
||||
"name": "model",
|
||||
"label": "Model",
|
||||
"type": "ENUM",
|
||||
"supported_operators": []string{"eq", "neq", "includes"},
|
||||
},
|
||||
},
|
||||
"field_groups": []map[string]any{
|
||||
{"name": "request", "label": "Request"},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("key", WithBaseURL(server.URL))
|
||||
resp, err := client.GetChatCompletionFields(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.FieldDefinitions) != 1 {
|
||||
t.Fatalf("got %d field definitions", len(resp.FieldDefinitions))
|
||||
}
|
||||
def := resp.FieldDefinitions[0]
|
||||
if def.Name != "model" || def.Type != observability.FieldTypeEnum {
|
||||
t.Errorf("unexpected field def: %+v", def)
|
||||
}
|
||||
if len(def.SupportedOperators) != 3 {
|
||||
t.Errorf("got %d operators", len(def.SupportedOperators))
|
||||
}
|
||||
if len(resp.FieldGroups) != 1 || resp.FieldGroups[0].Name != "request" {
|
||||
t.Errorf("unexpected groups: %+v", resp.FieldGroups)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChatCompletionFieldOptions_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/observability/chat-completion-fields/model/options" {
|
||||
t.Errorf("got path %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("operator"); got != "eq" {
|
||||
t.Errorf("got operator=%q want eq", got)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"options": []any{"mistral-small-latest", "mistral-large-latest", nil, true},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("key", WithBaseURL(server.URL))
|
||||
resp, err := client.GetChatCompletionFieldOptions(context.Background(), "model", observability.FieldOperatorEq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Options) != 4 {
|
||||
t.Fatalf("got %d options", len(resp.Options))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChatCompletionFieldOptionsCounts_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" || r.URL.Path != "/v1/observability/chat-completion-fields/model/options-counts" {
|
||||
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"counts": []map[string]any{
|
||||
{"value": "mistral-small-latest", "count": 42},
|
||||
{"value": "mistral-large-latest", "count": 17},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("key", WithBaseURL(server.URL))
|
||||
resp, err := client.GetChatCompletionFieldOptionsCounts(context.Background(), "model", &observability.FieldOptionCountsRequest{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Counts) != 2 || resp.Counts[0].Count != 42 {
|
||||
t.Errorf("unexpected counts: %+v", resp.Counts)
|
||||
}
|
||||
}
|
||||
61
workflow/connectors.go
Normal file
61
workflow/connectors.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package workflow
|
||||
|
||||
// ConnectorSlot declares a connector dependency for a workflow execution.
|
||||
//
|
||||
// Pass a slice of slots to BuildConnectorExtensions to produce the
|
||||
// nested map expected on ExecutionRequest.Extensions.
|
||||
type ConnectorSlot struct {
|
||||
ConnectorName string `json:"connector_name"`
|
||||
CredentialsName *string `json:"credentials_name,omitempty"`
|
||||
}
|
||||
|
||||
// ConnectorBindings is the bindings list inside ConnectorExtensions.
|
||||
type ConnectorBindings struct {
|
||||
Bindings []ConnectorSlot `json:"bindings"`
|
||||
}
|
||||
|
||||
// ConnectorExtensions is the value of the "mistralai" key in workflow extensions.
|
||||
type ConnectorExtensions struct {
|
||||
Connectors ConnectorBindings `json:"connectors"`
|
||||
}
|
||||
|
||||
// WorkflowExtensions is the top-level shape of the extensions field
|
||||
// expected by the workflow execute endpoint when binding connectors.
|
||||
type WorkflowExtensions struct {
|
||||
Mistralai ConnectorExtensions `json:"mistralai"`
|
||||
}
|
||||
|
||||
// BuildConnectorExtensions returns the value to set on
|
||||
// ExecutionRequest.Extensions for the given connector slots.
|
||||
//
|
||||
// The result is a map[string]any so callers can merge in additional
|
||||
// extension keys without colliding with the connector wire shape.
|
||||
func BuildConnectorExtensions(slots ...ConnectorSlot) map[string]any {
|
||||
return map[string]any{
|
||||
"mistralai": ConnectorExtensions{
|
||||
Connectors: ConnectorBindings{Bindings: slots},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectorAuthStatus is the state of an OAuth flow emitted by a
|
||||
// connector-auth custom task event.
|
||||
type ConnectorAuthStatus string
|
||||
|
||||
const (
|
||||
ConnectorAuthWaitingForAuth ConnectorAuthStatus = "waiting_for_auth"
|
||||
ConnectorAuthConnected ConnectorAuthStatus = "connected"
|
||||
ConnectorAuthAccessDenied ConnectorAuthStatus = "access_denied"
|
||||
ConnectorAuthTimedOut ConnectorAuthStatus = "timed_out"
|
||||
ConnectorAuthError ConnectorAuthStatus = "error"
|
||||
)
|
||||
|
||||
// ConnectorAuthTaskState is the payload of a custom task event of type
|
||||
// "connector-auth", emitted while a workflow waits for OAuth completion.
|
||||
type ConnectorAuthTaskState struct {
|
||||
ConnectorName string `json:"connector_name"`
|
||||
ConnectorID string `json:"connector_id"`
|
||||
Status ConnectorAuthStatus `json:"status"`
|
||||
AuthURL *string `json:"auth_url,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
}
|
||||
36
workflow/definition.go
Normal file
36
workflow/definition.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package workflow
|
||||
|
||||
// CodeDefinition describes a workflow's code-level interface: its input/output
|
||||
// schemas, signal/query/update handlers, and execution constraints.
|
||||
type CodeDefinition struct {
|
||||
InputSchema map[string]any `json:"input_schema"`
|
||||
OutputSchema map[string]any `json:"output_schema,omitempty"`
|
||||
Signals []SignalDefinition `json:"signals,omitempty"`
|
||||
Queries []QueryDefinition `json:"queries,omitempty"`
|
||||
Updates []UpdateDefinition `json:"updates,omitempty"`
|
||||
EnforceDeterminism bool `json:"enforce_determinism,omitempty"`
|
||||
ExecutionTimeout *float64 `json:"execution_timeout,omitempty"`
|
||||
}
|
||||
|
||||
// SignalDefinition describes a signal handler on a workflow.
|
||||
type SignalDefinition struct {
|
||||
Name string `json:"name"`
|
||||
InputSchema map[string]any `json:"input_schema"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// QueryDefinition describes a query handler on a workflow.
|
||||
type QueryDefinition struct {
|
||||
Name string `json:"name"`
|
||||
InputSchema map[string]any `json:"input_schema"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
OutputSchema map[string]any `json:"output_schema,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateDefinition describes an update handler on a workflow.
|
||||
type UpdateDefinition struct {
|
||||
Name string `json:"name"`
|
||||
InputSchema map[string]any `json:"input_schema"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
OutputSchema map[string]any `json:"output_schema,omitempty"`
|
||||
}
|
||||
166
workflow/definition_test.go
Normal file
166
workflow/definition_test.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCodeDefinition_RoundTrip(t *testing.T) {
|
||||
raw := `{
|
||||
"input_schema": {"type": "object", "properties": {"prompt": {"type": "string"}}},
|
||||
"output_schema": {"type": "object", "properties": {"result": {"type": "string"}}},
|
||||
"signals": [
|
||||
{"name": "cancel", "input_schema": {"type": "object"}, "description": "Cancel the workflow"}
|
||||
],
|
||||
"queries": [
|
||||
{"name": "status", "input_schema": {"type": "object"}, "description": "Get status", "output_schema": {"type": "string"}}
|
||||
],
|
||||
"updates": [
|
||||
{"name": "set_priority", "input_schema": {"type": "object", "properties": {"level": {"type": "integer"}}}, "description": "Set priority", "output_schema": null}
|
||||
],
|
||||
"enforce_determinism": true,
|
||||
"execution_timeout": 3600.5
|
||||
}`
|
||||
|
||||
var def CodeDefinition
|
||||
if err := json.Unmarshal([]byte(raw), &def); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if def.InputSchema == nil {
|
||||
t.Fatal("InputSchema is nil")
|
||||
}
|
||||
if def.OutputSchema == nil {
|
||||
t.Fatal("OutputSchema is nil")
|
||||
}
|
||||
if len(def.Signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(def.Signals))
|
||||
}
|
||||
if def.Signals[0].Name != "cancel" {
|
||||
t.Errorf("signal name = %q, want cancel", def.Signals[0].Name)
|
||||
}
|
||||
if def.Signals[0].Description == nil || *def.Signals[0].Description != "Cancel the workflow" {
|
||||
t.Errorf("signal description wrong")
|
||||
}
|
||||
if len(def.Queries) != 1 {
|
||||
t.Fatalf("expected 1 query, got %d", len(def.Queries))
|
||||
}
|
||||
if def.Queries[0].Name != "status" {
|
||||
t.Errorf("query name = %q, want status", def.Queries[0].Name)
|
||||
}
|
||||
if def.Queries[0].OutputSchema == nil {
|
||||
t.Error("query OutputSchema is nil, expected non-nil")
|
||||
}
|
||||
if len(def.Updates) != 1 {
|
||||
t.Fatalf("expected 1 update, got %d", len(def.Updates))
|
||||
}
|
||||
if def.Updates[0].Name != "set_priority" {
|
||||
t.Errorf("update name = %q, want set_priority", def.Updates[0].Name)
|
||||
}
|
||||
if def.EnforceDeterminism != true {
|
||||
t.Error("EnforceDeterminism should be true")
|
||||
}
|
||||
if def.ExecutionTimeout == nil || *def.ExecutionTimeout != 3600.5 {
|
||||
t.Errorf("ExecutionTimeout = %v, want 3600.5", def.ExecutionTimeout)
|
||||
}
|
||||
|
||||
// Re-marshal and verify round-trip
|
||||
out, err := json.Marshal(def)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var def2 CodeDefinition
|
||||
if err := json.Unmarshal(out, &def2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(def2.Signals) != 1 || def2.Signals[0].Name != "cancel" {
|
||||
t.Error("round-trip failed for signals")
|
||||
}
|
||||
if def2.EnforceDeterminism != true {
|
||||
t.Error("round-trip failed for enforce_determinism")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodeDefinition_MinimalFields(t *testing.T) {
|
||||
raw := `{"input_schema": {"type": "object"}}`
|
||||
|
||||
var def CodeDefinition
|
||||
if err := json.Unmarshal([]byte(raw), &def); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if def.InputSchema == nil {
|
||||
t.Fatal("InputSchema is nil")
|
||||
}
|
||||
if def.OutputSchema != nil {
|
||||
t.Errorf("OutputSchema should be nil, got %v", def.OutputSchema)
|
||||
}
|
||||
if def.Signals != nil {
|
||||
t.Errorf("Signals should be nil, got %v", def.Signals)
|
||||
}
|
||||
if def.EnforceDeterminism != false {
|
||||
t.Error("EnforceDeterminism should default to false")
|
||||
}
|
||||
if def.ExecutionTimeout != nil {
|
||||
t.Errorf("ExecutionTimeout should be nil, got %v", def.ExecutionTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistration_NewFields(t *testing.T) {
|
||||
raw := `{
|
||||
"id": "reg-1",
|
||||
"workflow_id": "wf-1",
|
||||
"task_queue": "legacy-queue",
|
||||
"deployment_id": "dep-abc",
|
||||
"compatible_with_chat_assistant": true,
|
||||
"definition": {
|
||||
"input_schema": {"type": "object"},
|
||||
"enforce_determinism": false
|
||||
},
|
||||
"created_at": "2026-04-01T00:00:00Z",
|
||||
"updated_at": "2026-04-02T00:00:00Z"
|
||||
}`
|
||||
|
||||
var reg Registration
|
||||
if err := json.Unmarshal([]byte(raw), ®); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if reg.ID != "reg-1" {
|
||||
t.Errorf("ID = %q", reg.ID)
|
||||
}
|
||||
if reg.DeploymentID == nil || *reg.DeploymentID != "dep-abc" {
|
||||
t.Errorf("DeploymentID = %v, want dep-abc", reg.DeploymentID)
|
||||
}
|
||||
if reg.CompatibleWithChatAssistant != true {
|
||||
t.Error("CompatibleWithChatAssistant should be true")
|
||||
}
|
||||
if reg.Definition == nil {
|
||||
t.Fatal("Definition is nil")
|
||||
}
|
||||
if reg.Definition.InputSchema == nil {
|
||||
t.Error("Definition.InputSchema is nil")
|
||||
}
|
||||
// TaskQueue still works for backward compat
|
||||
if reg.TaskQueue != "legacy-queue" {
|
||||
t.Errorf("TaskQueue = %q, want legacy-queue", reg.TaskQueue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistration_NullDeploymentID(t *testing.T) {
|
||||
raw := `{
|
||||
"id": "reg-2",
|
||||
"workflow_id": "wf-2",
|
||||
"task_queue": "q",
|
||||
"definition": {"input_schema": {"type": "object"}}
|
||||
}`
|
||||
|
||||
var reg Registration
|
||||
if err := json.Unmarshal([]byte(raw), ®); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if reg.DeploymentID != nil {
|
||||
t.Errorf("DeploymentID should be nil, got %v", reg.DeploymentID)
|
||||
}
|
||||
if reg.CompatibleWithChatAssistant != false {
|
||||
t.Error("CompatibleWithChatAssistant should default to false")
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,9 @@ type ExecutionRequest struct {
|
||||
TimeoutSeconds *float64 `json:"timeout_seconds,omitempty"`
|
||||
CustomTracingAttributes map[string]string `json:"custom_tracing_attributes,omitempty"`
|
||||
DeploymentName *string `json:"deployment_name,omitempty"`
|
||||
// Extensions carries plugin-specific data such as connector bindings.
|
||||
// Use BuildConnectorExtensions to construct the standard connector shape.
|
||||
Extensions map[string]any `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// ExecutionResponse is the response from a workflow execution.
|
||||
@@ -40,11 +43,20 @@ type ExecutionResponse struct {
|
||||
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
|
||||
}
|
||||
|
||||
// EncodedPayloadOption identifies how a workflow payload was encoded.
|
||||
type EncodedPayloadOption string
|
||||
|
||||
const (
|
||||
EncodedPayloadOffloaded EncodedPayloadOption = "offloaded"
|
||||
EncodedPayloadEncrypted EncodedPayloadOption = "encrypted"
|
||||
EncodedPayloadEncryptedPartial EncodedPayloadOption = "encrypted-partial"
|
||||
)
|
||||
|
||||
// NetworkEncodedInput holds a base64-encoded payload for workflow input.
|
||||
type NetworkEncodedInput struct {
|
||||
B64Payload string `json:"b64payload"`
|
||||
EncodingOptions []string `json:"encoding_options,omitempty"`
|
||||
Empty bool `json:"empty,omitempty"`
|
||||
B64Payload string `json:"b64payload"`
|
||||
EncodingOptions []EncodedPayloadOption `json:"encoding_options,omitempty"`
|
||||
Empty bool `json:"empty,omitempty"`
|
||||
}
|
||||
|
||||
// SignalInvocationBody is the request body for signaling a workflow execution.
|
||||
|
||||
@@ -2,8 +2,12 @@ package workflow
|
||||
|
||||
// Registration represents a workflow registration.
|
||||
type Registration struct {
|
||||
ID string `json:"id"`
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
ID string `json:"id"`
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
Definition *CodeDefinition `json:"definition,omitempty"`
|
||||
DeploymentID *string `json:"deployment_id,omitempty"`
|
||||
CompatibleWithChatAssistant bool `json:"compatible_with_chat_assistant,omitempty"`
|
||||
// Deprecated: use DeploymentID instead. Will be removed in a future release.
|
||||
TaskQueue string `json:"task_queue"`
|
||||
Workflow *Workflow `json:"workflow,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
@@ -35,10 +39,3 @@ type RegistrationGetParams struct {
|
||||
WithWorkflow *bool
|
||||
IncludeShared *bool
|
||||
}
|
||||
|
||||
// WorkerInfo holds information about the current worker.
|
||||
type WorkerInfo struct {
|
||||
SchedulerURL string `json:"scheduler_url"`
|
||||
Namespace string `json:"namespace"`
|
||||
TLS bool `json:"tls"`
|
||||
}
|
||||
|
||||
12
workflow/worker.go
Normal file
12
workflow/worker.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package workflow
|
||||
|
||||
// WorkerInfo describes the worker scheduler the SDK is connected to.
|
||||
//
|
||||
// Returned by GET /v1/workflows/workers/whoami. Useful when running custom
|
||||
// workers that need to know which scheduler / namespace to connect to.
|
||||
// For managed deployments, prefer Registration.DeploymentID.
|
||||
type WorkerInfo struct {
|
||||
SchedulerURL string `json:"scheduler_url"`
|
||||
Namespace string `json:"namespace"`
|
||||
TLS bool `json:"tls"`
|
||||
}
|
||||
106
workflows_extensions_test.go
Normal file
106
workflows_extensions_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/VikingOwl91/mistral-go-sdk/conversation"
|
||||
"github.com/VikingOwl91/mistral-go-sdk/workflow"
|
||||
)
|
||||
|
||||
func TestNetworkEncodedInput_EncodingOptions(t *testing.T) {
|
||||
in := workflow.NetworkEncodedInput{
|
||||
B64Payload: "eyJrIjoidiJ9",
|
||||
EncodingOptions: []workflow.EncodedPayloadOption{workflow.EncodedPayloadOffloaded, workflow.EncodedPayloadEncrypted},
|
||||
}
|
||||
b, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got map[string]any
|
||||
if err := json.Unmarshal(b, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opts, ok := got["encoding_options"].([]any)
|
||||
if !ok || len(opts) != 2 {
|
||||
t.Fatalf("unexpected encoding_options: %v", got["encoding_options"])
|
||||
}
|
||||
if opts[0] != "offloaded" || opts[1] != "encrypted" {
|
||||
t.Errorf("got %v, want [offloaded encrypted]", opts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildConnectorExtensions_WireShape(t *testing.T) {
|
||||
creds := "work-account"
|
||||
ext := workflow.BuildConnectorExtensions(
|
||||
workflow.ConnectorSlot{ConnectorName: "gmail"},
|
||||
workflow.ConnectorSlot{ConnectorName: "notion", CredentialsName: &creds},
|
||||
)
|
||||
b, err := json.Marshal(ext)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := `{"mistralai":{"connectors":{"bindings":[{"connector_name":"gmail"},{"connector_name":"notion","credentials_name":"work-account"}]}}}`
|
||||
if string(b) != want {
|
||||
t.Errorf("\nwant %s\ngot %s", want, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutionRequest_Extensions(t *testing.T) {
|
||||
req := workflow.ExecutionRequest{
|
||||
Extensions: workflow.BuildConnectorExtensions(workflow.ConnectorSlot{ConnectorName: "gmail"}),
|
||||
}
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got map[string]any
|
||||
if err := json.Unmarshal(b, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := got["extensions"]; !ok {
|
||||
t.Fatalf("expected extensions key in marshalled request: %s", string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectorAuthTaskState_Roundtrip(t *testing.T) {
|
||||
authURL := "https://oauth.example.com/authorize"
|
||||
in := workflow.ConnectorAuthTaskState{
|
||||
ConnectorName: "gmail",
|
||||
ConnectorID: "conn-1",
|
||||
Status: workflow.ConnectorAuthWaitingForAuth,
|
||||
AuthURL: &authURL,
|
||||
}
|
||||
b, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var out workflow.ConnectorAuthTaskState
|
||||
if err := json.Unmarshal(b, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if out.Status != workflow.ConnectorAuthWaitingForAuth {
|
||||
t.Errorf("got status %q", out.Status)
|
||||
}
|
||||
if out.AuthURL == nil || *out.AuthURL != authURL {
|
||||
t.Errorf("auth_url roundtrip failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmationConstants_WireValues(t *testing.T) {
|
||||
// Reply constants.
|
||||
c := conversation.ToolCallConfirmation{
|
||||
ToolCallID: "call_1",
|
||||
Confirmation: string(conversation.ConfirmationAllow),
|
||||
}
|
||||
b, _ := json.Marshal(c)
|
||||
if string(b) != `{"tool_call_id":"call_1","confirmation":"allow"}` {
|
||||
t.Errorf("got %s", string(b))
|
||||
}
|
||||
// Inbound status constants.
|
||||
if conversation.ConfirmationStatusPending != "pending" ||
|
||||
conversation.ConfirmationStatusAllowed != "allowed" ||
|
||||
conversation.ConfirmationStatusDenied != "denied" {
|
||||
t.Errorf("unexpected confirmation status constants")
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,11 @@ import (
|
||||
"github.com/VikingOwl91/mistral-go-sdk/workflow"
|
||||
)
|
||||
|
||||
// GetWorkflowWorkerInfo retrieves information about the current worker.
|
||||
// GetWorkflowWorkerInfo returns the scheduler URL, namespace, and TLS setting
|
||||
// the API expects custom workers to connect with.
|
||||
//
|
||||
// Most callers using managed deployments do not need this — see
|
||||
// Registration.DeploymentID. It is exposed for users running custom workers.
|
||||
func (c *Client) GetWorkflowWorkerInfo(ctx context.Context) (*workflow.WorkerInfo, error) {
|
||||
var resp workflow.WorkerInfo
|
||||
if err := c.doJSON(ctx, "GET", "/v1/workflows/workers/whoami", nil, &resp); err != nil {
|
||||
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
|
||||
func TestGetWorkflowWorkerInfo_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/workflows/workers/whoami" {
|
||||
t.Errorf("got path %s", r.URL.Path)
|
||||
if r.Method != "GET" || r.URL.Path != "/v1/workflows/workers/whoami" {
|
||||
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"scheduler_url": "https://scheduler.mistral.ai",
|
||||
"namespace": "default",
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"scheduler_url": "scheduler.example.com:7233",
|
||||
"namespace": "tenant-2",
|
||||
"tls": true,
|
||||
})
|
||||
}))
|
||||
@@ -26,10 +26,27 @@ func TestGetWorkflowWorkerInfo_Success(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if info.Namespace != "default" {
|
||||
t.Errorf("got namespace %q", info.Namespace)
|
||||
}
|
||||
if !info.TLS {
|
||||
t.Error("expected tls=true")
|
||||
if info.SchedulerURL != "scheduler.example.com:7233" || info.Namespace != "tenant-2" || !info.TLS {
|
||||
t.Errorf("unexpected info: %+v", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWorkflowWorkerInfo_TLSDefault(t *testing.T) {
|
||||
// Server omits the tls field; the SDK should default it to false.
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"scheduler_url": "s",
|
||||
"namespace": "n",
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("key", WithBaseURL(server.URL))
|
||||
info, err := client.GetWorkflowWorkerInfo(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if info.TLS {
|
||||
t.Errorf("expected default tls=false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user